package cn.dansj.common.utils.spring.request;

import cn.dansj.common.utils.json.Feature;
import cn.dansj.common.utils.json.JSONUtils;
import cn.dansj.common.utils.transfer.Transformation;
import org.apache.hc.client5.http.HttpRequestRetryStrategy;
import org.apache.hc.client5.http.config.ConnectionConfig;
import org.apache.hc.client5.http.config.RequestConfig;
import org.apache.hc.client5.http.impl.DefaultConnectionKeepAliveStrategy;
import org.apache.hc.client5.http.impl.DefaultHttpRequestRetryStrategy;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager;
import org.apache.hc.client5.http.socket.ConnectionSocketFactory;
import org.apache.hc.client5.http.socket.PlainConnectionSocketFactory;
import org.apache.hc.client5.http.ssl.HttpsSupport;
import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory;
import org.apache.hc.core5.http.Header;
import org.apache.hc.core5.http.config.Registry;
import org.apache.hc.core5.http.config.RegistryBuilder;
import org.apache.hc.core5.http.io.SocketConfig;
import org.apache.hc.core5.http.message.BasicHeader;
import org.apache.hc.core5.ssl.SSLContextBuilder;
import org.apache.hc.core5.util.TimeValue;
import org.apache.hc.core5.util.Timeout;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.client.ClientHttpRequestFactory;

import javax.net.ssl.SSLContext;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

public class ClientHttpRequestFactoryUtils {
    private static final Logger logger = LoggerFactory.getLogger(ClientHttpRequestFactoryUtils.class);

    public static ClientHttpRequestFactory create() {
        return create(null);
    }

    public static ClientHttpRequestFactory create(HttpClientPoolConfig httpClientPoolConfig) {
        httpClientPoolConfig = httpClientPoolConfig == null ? new HttpClientPoolConfig() : httpClientPoolConfig;
        CloseableHttpClient httpClient = buildHttpClient(httpClientPoolConfig);
        HttpComponentsClientHttpRequestFactory httpComponentsClientHttpRequestFactory = new HttpComponentsClientHttpRequestFactory(httpClient);
        httpComponentsClientHttpRequestFactory.setConnectionRequestTimeout(httpClientPoolConfig.getConnectionRequestTimeout()); // 不宜过长
        httpComponentsClientHttpRequestFactory.setConnectTimeout(httpClientPoolConfig.getConnectionTimeout());
        logger.info("HttpComponentsClientHttpRequestFactory ==>\n{}", JSONUtils.toJSON(httpComponentsClientHttpRequestFactory, Feature.PRETTY_PRINT));
        return httpComponentsClientHttpRequestFactory;
    }

    private static CloseableHttpClient buildHttpClient(HttpClientPoolConfig httpClientPoolConfig) {
        // 关键优化点：生产环境应配置可信SSL，此处保留方法名但增强安全性
        SSLContext sslContext = Transformation.lambdaTryCatch(() -> SSLContextBuilder.create().loadTrustMaterial((chain, authType) -> httpClientPoolConfig.isTrustAllCertificates()) // 通过配置控制
                .build(), () -> null);
        // 1. 连接层超时配置（TCP 连接建立）  // setConnectTimeout替代RequestConfig的 setConnectTimeout
        ConnectionConfig connectionConfig = ConnectionConfig.custom().setConnectTimeout(Timeout.ofMilliseconds(httpClientPoolConfig.getConnectionTimeout()))
                // 空闲连接校验
                .setValidateAfterInactivity(TimeValue.ofSeconds(30)).build();
        // 2. 请求层超时配置（从连接池获取连接、等待响应）
        RequestConfig requestConfig = RequestConfig.custom().setConnectionRequestTimeout(Timeout.ofMilliseconds(httpClientPoolConfig.getConnectionRequestTimeout())) // 从连接池获取连接的等待时间
                .setResponseTimeout(Timeout.ofMilliseconds(httpClientPoolConfig.getSocketTimeout()))         // 等待服务器响应的总时间
                .build();
        SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(sslContext, httpClientPoolConfig.isStrictHostnameVerification() ?
                HttpsSupport.getDefaultHostnameVerifier() : (hostname, session) -> true);
        Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create().register("http", PlainConnectionSocketFactory.getSocketFactory()).register("https", sslConnectionSocketFactory).build();
        PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(registry);
        // 连接池最大连接数
        connectionManager.setMaxTotal(httpClientPoolConfig.getMaxTotal());
        // 同路由并发数
        connectionManager.setDefaultMaxPerRoute(httpClientPoolConfig.getMaxPerRoute());
        // socket超时时间
        connectionManager.setDefaultSocketConfig(SocketConfig.custom().setSoTimeout(httpClientPoolConfig.getSocketTimeout(), TimeUnit.MILLISECONDS).build());
        connectionManager.setDefaultConnectionConfig(connectionConfig); // 应用连接层配置
        logger.info("PoolingHttpClientConnectionManager ==>\n{}", JSONUtils.toJSON(connectionManager, Feature.PRETTY_PRINT));
        final HttpRequestRetryStrategy retryStrategy = new DefaultHttpRequestRetryStrategy(httpClientPoolConfig.getRetryTimes(), TimeValue.ofSeconds(httpClientPoolConfig.getRetryInterval()));
        // 注册资源释放钩子
        Runtime.getRuntime().addShutdownHook(new Thread(connectionManager::close));
        return HttpClients.custom().setDefaultRequestConfig(requestConfig).setConnectionManager(connectionManager).setRetryStrategy(retryStrategy).setKeepAliveStrategy(new DefaultConnectionKeepAliveStrategy()).setDefaultHeaders(defaultHeaders()).build();
    }

    private static List<Header> defaultHeaders() {
        List<Header> headers = new ArrayList<>();
        headers.add(new BasicHeader(HttpHeaders.ACCEPT, "*/*"));
        headers.add(new BasicHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON));
        headers.add(new BasicHeader(HttpHeaders.USER_AGENT, "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.114 Safari/537.36"));
        return headers;
    }

    public static class HttpClientPoolConfig {
        private int connectionRequestTimeout = 1000;
        // 连接超时
        private int connectionTimeout = 10000;
        // 同路由并发数 # 每个路由的最大连接数,如果只调用一个地址,可以将其设置为最大连接数
        private int maxPerRoute = 100;
        // 池最大数 连接池的最大连接数，0代表不限
        private int maxTotal = 200;
        // 数据读取超时时间
        private int socketTimeout = 60000;
        private int retryTimes = 1;

        private int retryInterval = 5;

        private boolean strictHostnameVerification = true; // 生产环境默认严格验证
        private boolean trustAllCertificates = false;      // 生产环境默认不信任所有证书

        public int getConnectionRequestTimeout() {
            return connectionRequestTimeout;
        }

        public void setConnectionRequestTimeout(int connectionRequestTimeout) {
            this.connectionRequestTimeout = connectionRequestTimeout;
        }

        public int getConnectionTimeout() {
            return connectionTimeout;
        }

        public void setConnectionTimeout(int connectionTimeout) {
            this.connectionTimeout = connectionTimeout;
        }

        public int getMaxPerRoute() {
            return maxPerRoute;
        }

        public void setMaxPerRoute(int maxPerRoute) {
            this.maxPerRoute = maxPerRoute;
        }

        public int getMaxTotal() {
            return maxTotal;
        }

        public void setMaxTotal(int maxTotal) {
            this.maxTotal = maxTotal;
        }

        public int getSocketTimeout() {
            return socketTimeout;
        }

        public void setSocketTimeout(int socketTimeout) {
            this.socketTimeout = socketTimeout;
        }

        public int getRetryTimes() {
            return retryTimes;
        }

        public void setRetryTimes(int retryTimes) {
            this.retryTimes = retryTimes;
        }

        public int getRetryInterval() {
            return retryInterval;
        }

        public void setRetryInterval(int retryInterval) {
            this.retryInterval = retryInterval;
        }

        public boolean isStrictHostnameVerification() {
            return strictHostnameVerification;
        }

        public void setStrictHostnameVerification(boolean strictHostnameVerification) {
            this.strictHostnameVerification = strictHostnameVerification;
        }

        public boolean isTrustAllCertificates() {
            return trustAllCertificates;
        }

        public void setTrustAllCertificates(boolean trustAllCertificates) {
            this.trustAllCertificates = trustAllCertificates;
        }

        @Override
        public String toString() {
            return JSONUtils.toJSON(this, Feature.PRETTY_PRINT);
        }
    }
}
